//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <arch.h>
#include <types.h>
#include <nandflash.h>
#include <xllp_dfc.h>
#include <xllp_clkmgr.h>
#include <serial.h>

void * memset(void *, int, int);
void * memcpy(void *, const void *, int);

//#define DBG_SHOW
#ifndef DBG_SHOW
#define SerialOutputString(x)
#define SerialOutputHex(x)
#endif // DBG_SHOW

#define BBT_LEN 1024

struct nand_info
{
    u8  maf_id;
    u8  dev_id;
    u32 chipsize;
    u32 pagesize;
    u32 blocksize;
    u32 oobsize;
    /* FIXME: the bbt length shouldb be (info->chipsize/info->blocksize) >> 2 */
    u8  bbt[BBT_LEN];
    u32 badblockpos;
    u32 badblockbytes;
    XLLP_DFC_CONTEXT_T *pcontext;
};

#define NAND_RELOC_MAX 127
#define NAND_RELOC_TABLE_OFFSET 0x3e00
#define NAND_VALID_HEADER       0x0000524e //= 'N' + ('R' * 256)

struct nand_rel_table
{
    u16 header;
    u16 total;
    struct
    {
        u16 from;
        u16 to;
    } rel[NAND_RELOC_MAX];
};

typedef enum
{
    NULLFUNC = 0,
    ZIMAGE   = 1,
    JFFS2    = 2,
    YAFFS    = 3
}WRITE_FUNC;

static XLLP_DFC_FLASH_SPECS_T flash_info_zylonite =
{
    {
        10, // tCH, Enable signal hold time.
        0,  // tCS, Enable signal setup time.
        20, // tWH, ND_nWE high duration.
        40, // tWP, ND_nWE pulse time.
        20, // tRH, ND_nRE high duration.
        40, // tRP, ND_nRE pulse width.
        11123, // tR = tR + tRR + tWB + 1, ND_nWE high to ND_nRE low for read.
        110,   // tWHR, ND_nWE high to ND_nRE low delay for status read.
        10,    // tAR, ND_ALE low to ND_nRE low delay.
    },
    1,   // Data flash bus arbiter enable (ND_ARB_EN).
    32,  // 32, Pages per block (PG_PER_BLK).
    0,   // Second cycle start, Row address start position (RA_START).
    2,   // 2 bytes, returned ID bytes(RD_ID_CNT).
    0,   // NAND, (ND_MODE)
    0,   // Chip select don't care bit (NCSX).
    512, // 512, Page size in bytes (PAGE_SZ).
    16,  // 16, Width of Flash memory (DWIDTH_M).
    16,  // 16, Width of flash controller(DWIDTH_C).
    4096, // Number of physical blocks in Flash
    // command codes

    0x0000, // Read
    0x0050, // Read1 unused, current DFC don't support
    0x1080, // Write, two cycle command
    0x0070, // Read status
    0x0090, // Read ID
    0xD060, // Erase, two cycle command
    0x00FF, // Reset
    0x002A, // Lock whole flash
    0x2423, // Unlock, two cycle command, supporting partial unlock
    0x007A, // Read block lock status
};

static XLLP_DFC_MODE_T DFC_mode =
{
    0, //disable DMA
    1, //enable ECC
    1, //enable SPARE
    0, //CS0
};

static XLLP_DFC_CONTEXT_T DFC_context =
{
    (P_XLLP_DFC_REG_T)(0x43100000), //DFC register
    &flash_info_zylonite,
    &DFC_mode,
    (P_XLLP_VUINT32_T)NULL,  //Do not set DFC GPIO here
    (P_XLLP_MFP_RM_DB_ID_T)NULL, //resouce manager is not supported yet.
    (P_XLLP_OST_T)(0x40A00000),  //OST register
};

static struct nand_info monahans_nand;

static u8 jffs2_oob[16] = {0xFF, 0xFF, 0x85, 0x19, 0x3, 0x20, 0x0, 0x0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

static struct nand_rel_table monahans_nand_rel_table;

static inline int is_bad_block(u32 block, struct nand_info *info)
{
    if (((info->bbt[block >> 2] >> ((block & 0x3) << 1)) & 0x03) != 0x00)
        return 1;
    return 0;
}

static inline void set_bbt(u32 block, u8 val, struct nand_info *info)
{
    val &= 0x3;
    info->bbt[block >> 2] |= val << ((block & 0x3) << 1);
}

static inline void mark_bad_block(u32 block, struct nand_info *info)
{
    /*FIXME: the buf lengh should be info->pagesize + info->oobsize */
    u8 buf[528];
    int bytes = 0;
    XLLP_UINT32_T badblock;

    memset(buf, 0xFF, info->pagesize + info->oobsize);
    while (bytes < info->badblockbytes) {
        memset(buf + info->badblockpos + bytes, 0x0, 1);
        bytes++;
    }
    XllpFlashDfcWrite(info->pcontext, buf, block * (info->blocksize / info->pagesize), 1, 0, &badblock);
    set_bbt(block, 0x3, info);
}

static void inline enable_hw_ecc(int enable, struct nand_info *info)
{
    if (!enable) {
        NDCR &= ~NDCR_ECC_EN;
        info->pcontext->pDFCMode->enableECC = 0;
    }
    else {
        NDCR |= NDCR_ECC_EN;
        info->pcontext->pDFCMode->enableECC = 1;
    }
}

static inline int is_buf_blank(u8 * buf, int size)
{
    int i = 0;

    while (i < size) {
        if (*((unsigned long *)(buf + i)) != 0xFFFFFFFF)
            return 0;
        i += 4;
    }
    if (i > size) {
        i -= 4;
        while (i < size) {
            if (*(buf + i) != 0xFF)
                return 0;
            i++;
        }
    }
    return 1;
}

static void scan_nand(struct nand_info *info)
{
    int len, addr = 0;
    XLLP_UINT32_T eccpage;
    XLLP_STATUS_T status;
    int i = 0;
    /* FIXME: the buf length should be info->pagesize + info->oobsize */
    u8 buf[528];

    /* for bbt, we have 2 bits for one block */
    len = (info->chipsize / info->blocksize) >> 2;
    /* FIXME: bbt is 1024 at most, maybe we can point sram region for it */
    len = (len > BBT_LEN) ? BBT_LEN : len;
    memset(info->bbt, 0x00, len);

    /* Because for blank page, the DB ECC error will be reported by DFC. In fact we only care OOB data which is not calculated by hardware ECC */
    enable_hw_ecc(0, info);
    while (addr < info->chipsize) {
        int bytes = 0;

        status = XllpFlashDfcRead(info->pcontext, buf, 528, addr/info->pagesize, 1, 0, &eccpage);
        if (status != XLLP_STATUS_SUCCESS) {
            SerialOutputString("Read page error at address: 0x");
            SerialOutputHex(addr);
            SerialOutputString("\n");
            addr += info->blocksize;
            continue;
        }
        /* factory marked bad block ? */
        while (bytes < info->badblockbytes) {
            if (buf[info->badblockpos + bytes] != 0xFF)
                break;
            bytes++;
        }
        if (bytes < info->badblockbytes) {
            int block = addr / info->blocksize;
            set_bbt(block, 0x03, info);
            SerialOutputString("find bad block at address: 0x");
            SerialOutputHex(addr);
            SerialOutputString("\n");
        }
        addr += info->blocksize;
    }

    /* Read relocation table founded by XDEBUG */
    XllpFlashDfcRead(info->pcontext, buf, 528,
                    NAND_RELOC_TABLE_OFFSET/info->pagesize, 1, 0, &eccpage);
    memcpy((u8 *)&monahans_nand_rel_table,
                    buf, sizeof(monahans_nand_rel_table));
    if (monahans_nand_rel_table.header != NAND_VALID_HEADER)
        goto out;

    while (i < monahans_nand_rel_table.total) {
        int block = monahans_nand_rel_table.rel[i].from;
        if (!is_bad_block(block, info)) {
            SerialOutputString("find bad block at address: 0x");
            SerialOutputHex(block * info->blocksize);
            SerialOutputString("\n");
            mark_bad_block(block, info);
        }
        i++;
    }
out:
    enable_hw_ecc(1, info);
}

/*
   read from addr at flash, the read length is len. Because there may be
   bad blocks in the flash, the actual read length may less than len.
   We only read the page. The OOB is not read.
*/
int monahans_nand_read(u8 *buf, u32 addr, u32 len, int oob, struct nand_info *info)
{
    int block = -1, ofs = 0;
    u32 end_addr;
    XLLP_UINT32_T eccpage;
    XLLP_STATUS_T status;

    if (addr > info->chipsize || (addr + len) > info->chipsize)
        return 0;

    SerialOutputString("Read flash from 0x");
    SerialOutputHex(addr);
    SerialOutputString(", length 0x");
    SerialOutputHex(len);
    SerialOutputString("\n");
    /* align to page */
    addr &= ~(info->pagesize - 1);
    len &= ~(info->pagesize -1);
    end_addr = addr + len;
    while (addr < end_addr) {
        if (block != ((addr) & ~(info->blocksize - 1))) {
            block = (addr) & ~(info->blocksize - 1);
            if (is_bad_block(block / info->blocksize, info)) {
                addr = (addr & ~(info->blocksize -1)) + info->blocksize;
                continue;
            }
            SerialOutputString(".");
        }

        if (oob) {
            enable_hw_ecc(0, info);
            status = XllpFlashDfcRead(info->pcontext, buf + ofs, info->pagesize + info->oobsize, addr / info->pagesize, 1, 0, &eccpage);
            enable_hw_ecc(1, info);
            ofs += info->oobsize;
        }
        else
            status = XllpFlashDfcRead(info->pcontext, buf + ofs, info->pagesize,
                            addr / info->pagesize, 1, 0, &eccpage);

        if (status != XLLP_STATUS_SUCCESS &&
            (eccpage != addr / info->pagesize ||
            !is_buf_blank(buf + ofs, info->pagesize))) {
            SerialOutputString("error read page at address: 0x");
            SerialOutputHex(addr);
            SerialOutputString("\n");
            return ofs;
        }
        ofs += info->pagesize;
        addr += info->pagesize;
    }
    SerialOutputString("Done\n");
    return ofs;
}

int monahans_nand_erase(u32 addr, u32 len, WRITE_FUNC func,
                struct nand_info *info)
{
    u32 end_addr = 0;
    int ret = 0;
    /* FIXME: the buf size should be info->pagesize + info->oobsize */
    u8 temp_buf[528];

    if (addr & info->blocksize) {
        addr &= ~(info->blocksize - 1);
        SerialOutputString("address not align with block size, change to 0x");
        SerialOutputHex(addr);
        SerialOutputString("\n");
    }
    SerialOutputString("Erase flash from 0x");
    SerialOutputHex(addr);
    SerialOutputString(", length 0x");
    SerialOutputHex(len);
    SerialOutputString("\n");
    end_addr = addr + len;
    for (end_addr = addr + len; addr < end_addr; addr += info->blocksize) {
        u32 block;
        XLLP_STATUS_T status;
        XLLP_UINT32_T badblock;

        SerialOutputString(".");
        block = (addr) & ~(info->blocksize - 1);
        if (is_bad_block(block / info->blocksize, info)) {
            SerialOutputString("\nSkip for bad block #");
            SerialOutputHex(block);
            SerialOutputString("\n");
            continue;
        }
        status = XllpFlashDfcErase(info->pcontext, block / info->blocksize,
                        1, 0, &badblock);
        if (badblock == block / info->blocksize) {
            SerialOutputString("\nSkip for bad block #");
            SerialOutputHex(badblock);
            SerialOutputString("\n");
            mark_bad_block(block / info->blocksize, info);
            continue;
        }
        if (status != XLLP_STATUS_SUCCESS) {
            SerialOutputString("\nFailed\n");
            ret = -1;
            continue;
        }
        /* we may need write OOB */
        memset(temp_buf, 0xFF, info->pagesize);

        switch (func) {
            case ZIMAGE:
                break;
            case JFFS2:
                memcpy(temp_buf + info->pagesize, jffs2_oob, info->oobsize);
                /* fall down*/
            case YAFFS:
                /* FIXME: add YAFFS OOB data */
                enable_hw_ecc(0, info);
                XllpFlashDfcWrite(info->pcontext, temp_buf,
                                addr / info->pagesize, 1, 0, &badblock);
                enable_hw_ecc(1, info);
                break;
            case NULLFUNC:
                break;
        }
    }
    SerialOutputString("Done\n");
    return ret;
}

/*
   write from buf to addr of flash. The length of data for write in buf is len.
   In the blob, func defines what is writen to nand:
    zImage:  The linux kernel. OOB is not cared.
    jffs2 image: The jffs2 file system image. OOB should be writen.
    yaffs image: The yaffs file system image. OOB shoudl be writen.
   Because there may be bad block in flash, the actual length for writing in
   flash may be longer than len.
*/
int monahans_nand_write(u8 *buf, u32 addr, u32 len, WRITE_FUNC func, struct nand_info *info)
{
    int block = -1, ofs = 0;
    u32 end_addr;
    XLLP_UINT32_T badblock;
    /* FIXME: the buf size should be info->pagesize + info->oobsize */
    u8 temp_buf[528];
    XLLP_STATUS_T status;

    if (addr > info->chipsize || (addr + len) > info->chipsize)
        return 0;

    /* addr aligns to block size for erasing */
    if (addr & info->pagesize) {
        addr &= ~(info->pagesize - 1);
        SerialOutputString("address not align with page size, change to 0x");
        SerialOutputHex(addr);
        SerialOutputString("\n");
    }
    /* first erase the needed block */
    monahans_nand_erase(addr, len, func, info);
    end_addr = addr + len;
    SerialOutputString("Write flash from 0x");
    SerialOutputHex(addr);
    SerialOutputString(", length 0x");
    SerialOutputHex(len);
    SerialOutputString("\n");
    /* first erase it for writing */
    while (ofs < len && addr < info->chipsize) {
        /* new block? */
        if (block != ((addr) & ~(info->blocksize - 1))) {
            block = (addr) & ~(info->blocksize - 1);
            if (is_bad_block(block / info->blocksize, info)) {
                SerialOutputString("\nSkip for bad block #");
                SerialOutputHex(block);
                SerialOutputString("\n");
                addr = (addr & ~(info->blocksize -1)) + info->blocksize;
                continue;
            }
            SerialOutputString(".");
        }
        /* for some images, there are padding bytes. we do not write */
        if (is_buf_blank(buf + ofs, info->pagesize)) {
            ofs += info->pagesize;
            addr += info->pagesize;
            continue;
        }
        memcpy(temp_buf, buf + ofs, info->pagesize);
        memset(temp_buf + info->pagesize, 0xFF, info->oobsize);
        status = XllpFlashDfcWrite(info->pcontext, temp_buf,
                        addr / info->pagesize, 1, 0, &badblock);
        if (badblock == addr / info->blocksize) {
            SerialOutputString("\nSkip for bad block #");
            SerialOutputHex(badblock );
            SerialOutputString("\n");
            mark_bad_block(addr / info->blocksize, info);
            addr = (addr & ~(info->blocksize -1)) + info->blocksize;
            continue;
        }

        if (status != XLLP_STATUS_SUCCESS) {
            SerialOutputString("\nFailed\n");
            return ofs;
        }
        ofs += info->pagesize;
        addr += info->pagesize;
    }
    SerialOutputString("Done\n");
    return ofs;
}

int cmd_nand_read(u32 ram_addr, u32 flash_ofs, u32 len)
{

// SerialOutputString("Read 0x");SerialOutputHex(len);
// SerialOutputString(" length data from flash: 0x");SerialOutputHex(flash_ofs);
// SerialOutputString(" to RAM address: 0x");SerialOutputHex(ram_addr);
// SerialOutputString("\n");
    return monahans_nand_read((u8 *)ram_addr, flash_ofs, len, 0, &monahans_nand);
}

int cmd_nand_write(u32 ram_addr, u32 flash_ofs, u32 len)
{
//    SerialOutputString("Write 0x");SerialOutputHex(len);
//    SerialOutputString(" length data to flash: 0x");SerialOutputHex(flash_ofs);
//    SerialOutputString(" from RAM address: 0x");SerialOutputHex(ram_addr);
//    SerialOutputString("\n");
    return monahans_nand_write((u8 *)ram_addr, flash_ofs, len, ZIMAGE,
                    &monahans_nand);
}

int init_monahans_nand_driver(void)
{
    XLLP_STATUS_T status;
    XLLP_UINT8_T manufacturer, device;

    monahans_nand.pcontext  = &DFC_context;
    monahans_nand.pagesize  = monahans_nand.pcontext->pFlashInfo->pageSize;
    monahans_nand.blocksize = monahans_nand.pcontext->pFlashInfo->pagePerBlock\
                                * monahans_nand.pagesize;
    monahans_nand.chipsize  =  monahans_nand.pcontext->pFlashInfo->numBlocks\
                                * monahans_nand.blocksize;
    monahans_nand.oobsize       = 16;
    monahans_nand.badblockpos   = 512;
    monahans_nand.badblockbytes = 2;

    /* enable NAND clock, default is on */
    CKENA |= CKENA_4_DFC;

    status = XllpFlashDfcInitNOGPIO(monahans_nand.pcontext);
    if (status != XLLP_STATUS_SUCCESS) {
        SerialOutputString("XllpFlashDfcInitNoGPIO() failed.\n");
        return -1;
    }

    XllpFlashDfcReadID(monahans_nand.pcontext, &manufacturer, &device);
// SerialOutputString("\nNAND flash(Manu=0x");SerialOutputHex(manufacturer);
// SerialOutputString(" Device=0x");SerialOutputHex(device);
// SerialOutputString(") detected!\n");
    monahans_nand.maf_id = manufacturer;
    monahans_nand.dev_id = device;
    scan_nand(&monahans_nand);

    return 0;
}
